{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1031accb",
   "metadata": {},
   "source": [
    "# `subgraph`의 입력과 출력을 변환하는 방법\n",
    "\n",
    "`subgraph` **상태**가 `parent graph` 상태와 완전히 독립적일 수 있습니다. \n",
    "\n",
    "즉, 두 그래프 간에 중복되는 상태 키(state keys) 가 없을 수 있습니다. \n",
    "\n",
    "이러한 경우에는 `subgraph`를 호출하기 전에 입력을 변환하고, 반환하기 전에 출력을 변환해야 합니다. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5e98914",
   "metadata": {},
   "source": [
    "## 환경설정"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f99850ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "# API 키를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API 키 정보 로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3355dd38",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH17-LangGraph-Modules\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "862478a0",
   "metadata": {},
   "source": [
    "## `graph`와 `subgraph` 정의\n",
    "\n",
    "다음과 같이 3개의 `graph`를 정의하겠습니다.\n",
    "\n",
    "- `parent graph`\n",
    "  \n",
    "- `parent graph` 에 의해 호출될 `child subgraph`\n",
    "\n",
    "- `child graph` 에 의해 호출될 `grandchild subgraph`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5a78503",
   "metadata": {},
   "source": [
    "## `grandchild` 정의"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "5e075d2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 상태 관리를 위한 TypedDict와 StateGraph 관련 모듈 임포트\n",
    "from typing_extensions import TypedDict\n",
    "from langgraph.graph.state import StateGraph, START, END\n",
    "\n",
    "\n",
    "# 손자 노드의 상태를 정의하는 TypedDict 클래스, 문자열 타입의 my_grandchild_key 포함\n",
    "class GrandChildState(TypedDict):\n",
    "    my_grandchild_key: str\n",
    "\n",
    "\n",
    "# 손자 노드의 상태를 처리하는 함수, 입력된 문자열에 인사말 추가\n",
    "def grandchild_1(state: GrandChildState) -> GrandChildState:\n",
    "    # 자식 또는 부모 키는 여기서 접근 불가\n",
    "    return {\"my_grandchild_key\": f'([GrandChild] {state[\"my_grandchild_key\"]})'}\n",
    "\n",
    "\n",
    "# 손자 노드의 상태 그래프 초기화\n",
    "grandchild = StateGraph(GrandChildState)\n",
    "\n",
    "# 상태 그래프에 손자 노드 추가\n",
    "grandchild.add_node(\"grandchild_1\", grandchild_1)\n",
    "\n",
    "# 시작 노드에서 손자 노드로의 엣지 연결\n",
    "grandchild.add_edge(START, \"grandchild_1\")\n",
    "\n",
    "# 손자 노드에서 종료 노드로의 엣지 연결\n",
    "grandchild.add_edge(\"grandchild_1\", END)\n",
    "\n",
    "# 정의된 상태 그래프 컴파일 및 실행 가능한 그래프 생성\n",
    "grandchild_graph = grandchild.compile()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c75c5756",
   "metadata": {},
   "source": [
    "그래프를 시각화 합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e83abea",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_teddynote.graphs import visualize_graph\n",
    "\n",
    "visualize_graph(grandchild_graph, xray=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad47dfe3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 그래프 호출\n",
    "for chunk in grandchild_graph.stream(\n",
    "    {\"my_grandchild_key\": \"Hi, Teddy!\"}, subgraphs=True\n",
    "):\n",
    "    print(chunk)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10b214ab",
   "metadata": {},
   "source": [
    "## `child` 정의"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9fab75eb",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 자식 상태 타입 정의를 위한 TypedDict 클래스\n",
    "class ChildState(TypedDict):\n",
    "    my_child_key: str\n",
    "\n",
    "\n",
    "# 손자 그래프 호출 및 상태 변환 함수, 자식 상태를 입력받아 변환된 자식 상태 반환\n",
    "def call_grandchild_graph(state: ChildState) -> ChildState:\n",
    "    # 참고: 부모 또는 손자 키는 여기서 접근 불가능\n",
    "    # 자식 상태 채널에서 손자 상태 채널로 상태 변환\n",
    "    grandchild_graph_input = {\"my_grandchild_key\": state[\"my_child_key\"]}\n",
    "    # 손자 상태 채널에서 자식 상태 채널로 상태 변환 후 결과 반환\n",
    "    grandchild_graph_output = grandchild_graph.invoke(grandchild_graph_input)\n",
    "    return {\"my_child_key\": f'([Child] {grandchild_graph_output[\"my_grandchild_key\"]})'}\n",
    "\n",
    "\n",
    "# 자식 상태 그래프 초기화\n",
    "child = StateGraph(ChildState)\n",
    "# 참고: 컴파일된 그래프 대신 함수 전달\n",
    "# 자식 그래프에 노드 추가 및 시작-종료 엣지 연결\n",
    "child.add_node(\"child_1\", call_grandchild_graph)\n",
    "child.add_edge(START, \"child_1\")\n",
    "child.add_edge(\"child_1\", END)\n",
    "# 자식 그래프 컴파일\n",
    "child_graph = child.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "daa4ba50",
   "metadata": {},
   "outputs": [],
   "source": [
    "visualize_graph(child_graph, xray=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5894b1c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# child_graph 그래프 호출\n",
    "for chunk in child_graph.stream({\"my_child_key\": \"Hi, Teddy!\"}, subgraphs=True):\n",
    "    print(chunk)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18ec6a76",
   "metadata": {},
   "source": [
    "`grandchild_graph`의 호출을 별도의 함수(`call_grandchild_graph`)로 감싸고 있습니다. \n",
    "\n",
    "이 함수는 grandchild 그래프를 호출하기 전에 입력 상태를 변환하고, grandchild 그래프의 출력을 다시 child 그래프 상태로 변환합니다. \n",
    "\n",
    "만약 이러한 변환 없이 `grandchild_graph`를 직접 `.add_node`에 전달하면, child와 grandchild 상태 간에 공유된 상태 키(State Key) 이 없기 때문에 LangGraph에서 오류가 발생하게 됩니다.\n",
    "\n",
    "**중요**\n",
    "\n",
    "`child subgraph` 와 `grandchild subgraph`는 `parent graph`와 공유되지 않는 자신만의 **독립적인** `state`를 가지고 있다는 점에 유의하시기 바랍니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75bf1ed9",
   "metadata": {},
   "source": [
    "## `parent` 정의"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "1b914571",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 부모 상태 타입 정의를 위한 TypedDict 클래스\n",
    "class ParentState(TypedDict):\n",
    "    my_parent_key: str\n",
    "\n",
    "\n",
    "# 부모 상태의 my_parent_key 값에 '[Parent1]' 문자열을 추가하는 변환 함수\n",
    "def parent_1(state: ParentState) -> ParentState:\n",
    "    # 참고: 자식 또는 손자 키는 여기서 접근 불가\n",
    "    return {\"my_parent_key\": f'([Parent1] {state[\"my_parent_key\"]})'}\n",
    "\n",
    "\n",
    "# 부모 상태의 my_parent_key 값에 '[Parent2]' 문자열을 추가하는 변환 함수\n",
    "def parent_2(state: ParentState) -> ParentState:\n",
    "    return {\"my_parent_key\": f'([Parent2] {state[\"my_parent_key\"]})'}\n",
    "\n",
    "\n",
    "# 부모 상태와 자식 상태 간의 데이터 변환 및 자식 그래프 호출 처리\n",
    "def call_child_graph(state: ParentState) -> ParentState:\n",
    "    # 부모 상태 채널(my_parent_key)에서 자식 상태 채널(my_child_key)로 상태 변환\n",
    "    child_graph_input = {\"my_child_key\": state[\"my_parent_key\"]}\n",
    "    # 자식 상태 채널(my_child_key)에서 부모 상태 채널(my_parent_key)로 상태 변환\n",
    "    child_graph_output = child_graph.invoke(child_graph_input)\n",
    "    return {\"my_parent_key\": child_graph_output[\"my_child_key\"]}\n",
    "\n",
    "\n",
    "# 부모 상태 그래프 초기화 및 노드 구성\n",
    "parent = StateGraph(ParentState)\n",
    "parent.add_node(\"parent_1\", parent_1)\n",
    "\n",
    "# 참고: 컴파일된 그래프가 아닌 함수를 전달\n",
    "parent.add_node(\"child\", call_child_graph)\n",
    "parent.add_node(\"parent_2\", parent_2)\n",
    "\n",
    "# 상태 그래프의 실행 흐름을 정의하는 엣지 구성\n",
    "parent.add_edge(START, \"parent_1\")\n",
    "parent.add_edge(\"parent_1\", \"child\")\n",
    "parent.add_edge(\"child\", \"parent_2\")\n",
    "parent.add_edge(\"parent_2\", END)\n",
    "\n",
    "# 구성된 부모 상태 그래프의 컴파일 및 실행 가능한 그래프 생성\n",
    "parent_graph = parent.compile()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f949634",
   "metadata": {},
   "source": [
    "그래프를 시각화 합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "da01775e",
   "metadata": {},
   "outputs": [],
   "source": [
    "visualize_graph(parent_graph, xray=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74c53786",
   "metadata": {},
   "source": [
    "`child_graph` 호출을 별도의 함수 `call_child_graph` 로 감싸고 있는데, 이 함수는 자식 그래프를 호출하기 전에 입력 상태를 변환하고 자식 그래프의 출력을 다시 부모 그래프 상태로 변환합니다. \n",
    "\n",
    "변환 없이 `child_graph`를 직접 `.add_node`에 전달하면 부모와 자식 상태 간에 공유된 상태 키(State Key) 이 없기 때문에 LangGraph에서 오류가 발생합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32b06e95",
   "metadata": {},
   "source": [
    "그럼, 부모 그래프를 실행하여 자식 및 손자 하위 그래프가 올바르게 호출되는지 확인해보겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "83396a7a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 그래프 실행 및 \"my_parent_key\" 매개변수를 통한 \"Hi, Teddy!\" 값 전달\n",
    "for chunk in parent_graph.stream({\"my_parent_key\": \"Hi, Teddy!\"}, subgraphs=True):\n",
    "    print(chunk)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
